<!DOCTYPE html>
<html lang="zh-CN">
	<head>
		<meta charset="UTF-8">
		<meta name="viewport" content="width=device-width, initial-scale=1.0">
		<title>XML 格式化工具</title>
		<link rel="icon" type="image/x-icon" href="/tools/static/favicon.ico">
		<style>
			body {
				font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', Arial, sans-serif;
				max-width: 1400px;
				margin: 0 auto;
				padding: 40px 20px;
				background: linear-gradient(135deg, #e0e7ff 0%, #f3e8ff 100%);
				color: #1f2937;
				position: relative;
			}

			h1 {
				color: #1e40af;
				text-align: center;
				margin-bottom: 40px;
				font-size: 2.5rem;
				font-weight: 700;
				text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.1);
			}

			.container {
				display: flex;
				gap: 30px;
				background: #ffffff;
				border-radius: 16px;
				padding: 30px;
				box-shadow: 0 10px 20px rgba(0, 0, 0, 0.1);
				backdrop-filter: blur(10px);
				background: rgba(255, 255, 255, 0.95);
			}

			.input-area,
			.output-area {
				flex: 1;
				position: relative;
			}

			h3 {
				color: #374151;
				margin-bottom: 15px;
				font-size: 1.25rem;
				font-weight: 600;
			}

			.textarea-wrapper {
				position: relative;
				background: #f9fafb;
				border-radius: 12px;
				border: 1px solid #d1d5db;
				transition: border-color 0.3s ease;
			}

			.textarea-wrapper:hover {
				border-color: #93c5fd;
			}

			textarea {
				width: 100%;
				height: 500px;
				padding: 15px;
				font-family: 'Fira Code', 'Courier New', monospace;
				font-size: 14px;
				font-weight: 500;
				line-height: 1.6;
				border: none;
				resize: vertical;
				box-sizing: border-box;
				background: transparent;
				color: #1f2937;
			}

			textarea:focus {
				outline: none;
				box-shadow: 0 0 0 3px rgba(59, 130, 246, 0.2);
			}

			.output-container {
				position: relative;
				height: 500px;
				overflow-y: auto;
				font-family: 'Fira Code', 'Courier New', monospace;
				font-size: 14px;
				font-weight: 500;
				line-height: 1.6;
				padding: 15px;
				background: #f9fafb;
				border-radius: 12px;
				border: 1px solid #d1d5db;
				transition: border-color 0.3s ease;
				/* 确保容器宽度不会被撑开 */
				box-sizing: border-box;
				overflow-x: auto;
				/* 允许水平滚动 */
			}

			.output-container:hover {
				border-color: #93c5fd;
			}

			.line {
				display: flex;
				align-items: flex-start;
				/* 确保长文本换行时对齐 */
			}

			.line-number {
				width: 40px;
				color: #6b7280;
				text-align: right;
				padding-right: 10px;
				border-right: 1px solid #e5e7eb;
				user-select: none;
				flex-shrink: 0;
				/* 防止行号被压缩 */
			}

			.line-content {
				flex: 1;
				padding-left: 15px;
				white-space: pre-wrap;
				/* 保留换行和空格，同时允许自动换行 */
				word-break: break-all;
				/* 强制长单词或连续字符换行 */
				color: #1f2937;
			}

			.buttons {
				margin: 20px 0;
				display: flex;
				gap: 12px;
				justify-content: center;
			}

			button {
				padding: 12px 24px;
				cursor: pointer;
				background: linear-gradient(45deg, #3b82f6, #2563eb);
				color: white;
				border: none;
				border-radius: 8px;
				font-size: 14px;
				font-weight: 600;
				transition: transform 0.2s, box-shadow 0.3s, background 0.3s;
			}

			button:hover {
				transform: translateY(-2px);
				box-shadow: 0 6px 12px rgba(0, 0, 0, 0.15);
				background: linear-gradient(45deg, #60a5fa, #3b82f6);
			}

			.copy-btn {
				background: linear-gradient(45deg, #34d399, #10b981);
			}

			.copy-btn:hover {
				background: linear-gradient(45deg, #6ee7b7, #34d399);
			}

			.error {
				color: #dc2626;
				margin-top: 10px;
				padding: 10px;
				background: #fee2e2;
				border-radius: 8px;
				display: none;
				font-size: 14px;
				white-space: pre-wrap;
			}

			.toast {
				position: fixed;
				top: 20px;
				right: 20px;
				background: #10b981;
				color: white;
				padding: 12px 24px;
				border-radius: 8px;
				box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
				display: flex;
				align-items: center;
				gap: 10px;
				font-size: 14px;
				font-weight: 500;
				opacity: 0;
				transform: translateY(-20px);
				transition: opacity 0.3s ease, transform 0.3s ease;
				z-index: 1000;
			}

			.toast.show {
				opacity: 1;
				transform: translateY(0);
			}

			.toast .close-btn {
				cursor: pointer;
				font-size: 16px;
				line-height: 1;
				color: #d1fae5;
			}

			.toast .close-btn:hover {
				color: #ffffff;
			}
		</style>
	</head>
	<body>
		<h1>XML 格式化工具</h1>
		<div class="container">
			<div class="input-area">
				<h3>输入 XML</h3>
				<div class="textarea-wrapper">
					<textarea id="inputXml" placeholder="在此输入XML内容..."></textarea>
				</div>
				<div class="buttons">
					<button onclick="formatXml()">格式化</button>
					<button onclick="compressXml()">压缩</button>
					<button onclick="clearInput()">清空</button>
				</div>
				<div id="errorMsg" class="error"></div>
			</div>
			<div class="output-area">
				<h3>输出结果</h3>
				<div id="outputXml" class="output-container"></div>
				<div class="buttons">
					<button class="copy-btn" onclick="copyOutput()">复制</button>
				</div>
			</div>
		</div>

		<script>
			let xmlData = null;

			function formatXml() {
				const input = document.getElementById('inputXml').value;
				const output = document.getElementById('outputXml');
				const errorMsg = document.getElementById('errorMsg');

				try {
					xmlData = parseXml(input);
					renderXml();
					errorMsg.style.display = 'none';
				} catch (e) {
					errorMsg.textContent = e.message;
					errorMsg.style.display = 'block';
					output.innerHTML = '';
				}
			}

			function compressXml() {
				const input = document.getElementById('inputXml').value;
				const output = document.getElementById('outputXml');
				const errorMsg = document.getElementById('errorMsg');

				try {
					// 简单的去除多余空格和换行
					const compressedXml = input.replace(/\s+/g, ' ').trim();
					output.innerHTML = '';
					const line = document.createElement('div');
					line.className = 'line';
					line.innerHTML = `
                    <span class="line-number">1</span>
                    <span class="line-content">${escapeXml(compressedXml)}</span>
                `;
					output.appendChild(line);
					errorMsg.style.display = 'none';
				} catch (e) {
					errorMsg.textContent = e.message;
					errorMsg.style.display = 'block';
					output.innerHTML = '';
				}
			}

			function clearInput() {
				document.getElementById('inputXml').value = '';
				document.getElementById('outputXml').innerHTML = '';
				document.getElementById('errorMsg').style.display = 'none';
			}

			function copyOutput() {
				const output = document.getElementById('outputXml');
				const text = Array.from(output.querySelectorAll('.line-content'))
						.map(span => span.textContent)
						.join('\n');

				// 降级方案：使用 textarea 和 document.execCommand 复制文本
				const unsecuredCopyToClipboard = (text) => {
					const textArea = document.createElement('textarea');
					textArea.value = text;
					textArea.style.position = 'fixed'; // 避免影响页面布局
					textArea.style.opacity = '0'; // 隐藏 textarea
					document.body.appendChild(textArea);
					textArea.focus();
					textArea.select();
					try {
						const successful = document.execCommand('copy');
						if (successful) {
							showToast('已复制到剪贴板', 'success');
						} else {
							showToast('复制失败，请手动复制', 'error');
						}
					} catch (err) {
						showToast('复制失败：' + err.message, 'error');
					}
					document.body.removeChild(textArea);
				};

				// 直接使用降级方案
				unsecuredCopyToClipboard(text);
			}

			// 改进的 XML 解析函数，捕获详细错误
			function parseXml(xmlString) {
				const parser = new DOMParser();
				const xmlDoc = parser.parseFromString(xmlString, 'application/xml');
				const parserError = xmlDoc.querySelector('parsererror');
				if (parserError) {
					const errorText = parserError.textContent || parserError.innerText;
					const lines = xmlString.split('\n');
					let errorMessage = 'XML 解析失败：\n';

					const lineMatch = errorText.match(/Line number (\d+)/i);
					const lineNumber = lineMatch ? parseInt(lineMatch[1], 10) : null;
					const errorDetailMatch = errorText.match(/error: (.+)/i);
					const errorDetail = errorDetailMatch ? errorDetailMatch[1] : errorText;

					if (lineNumber && lineNumber <= lines.length) {
						errorMessage += `错误发生在第 ${lineNumber} 行：\n`;
						errorMessage += `  ${lines[lineNumber - 1].trim()}\n`;
						errorMessage += `具体问题：${errorDetail}`;
					} else {
						errorMessage += `具体问题：${errorDetail}`;
					}

					throw new Error(errorMessage);
				}
				return xmlDoc;
			}

			function renderXml() {
				if (!xmlData) return;
				const output = document.getElementById('outputXml');
				output.innerHTML = '';
				let lineNumber = 1;

				// 处理 XML 声明（如果存在）
				const xmlDeclaration = xmlData.firstChild.nodeType === Node.PROCESSING_INSTRUCTION_NODE ?
					`<?${xmlData.firstChild.nodeName} ${xmlData.firstChild.nodeValue}?>` : '';
				if (xmlDeclaration) {
					const line = document.createElement('div');
					line.className = 'line';
					line.innerHTML = `
                    <span class="line-number">${lineNumber}</span>
                    <span class="line-content">${xmlDeclaration}</span>
                `;
					output.appendChild(line);
					lineNumber++;
				}

				// 渲染根节点
				lineNumber = renderXmlNode(xmlData.documentElement, 0, output, lineNumber).lineNumber;
			}

			function renderXmlNode(node, indent = 0, container, lineNumber) {
				const indentStr = '  '.repeat(indent); // 使用两个空格进行缩进，更易读
				let currentLine = lineNumber;

				if (node.nodeType === Node.ELEMENT_NODE) {
					const tagName = node.tagName;
					const attributes = Array.from(node.attributes)
						.map(attr => `${attr.name}="${attr.value}"`)
						.join(' ');
					const hasChildren = node.childNodes.length > 0;

					// 开标签
					const openTag = `<${tagName}${attributes ? ' ' + attributes : ''}${!hasChildren ? '/' : ''}>`;
					const openLine = document.createElement('div');
					openLine.className = 'line';
					openLine.innerHTML = `
                    <span class="line-number">${currentLine}</span>
                    <span class="line-content">${indentStr}<span style="color: #a71d5d;">&lt;</span><span style="color: #7952b3;">${tagName}</span>${attributes ? ' <span style="color: #0086b3;">' + attributes + '</span>' : ''}${!hasChildren ? ' <span style="color: #a71d5d;">/&gt;</span>' : '<span style="color: #a71d5d;">&gt;</span>'}</span>
                `;
					container.appendChild(openLine);
					currentLine++;

					if (hasChildren) {
						// 处理子节点
						for (let i = 0; i < node.childNodes.length; i++) {
							const child = node.childNodes[i];
							if (child.nodeType === Node.ELEMENT_NODE) {
								const result = renderXmlNode(child, indent + 1, container, currentLine);
								currentLine = result.lineNumber;
							} else if (child.nodeType === Node.TEXT_NODE && child.nodeValue.trim()) {
								const textLine = document.createElement('div');
								textLine.className = 'line';
								textLine.innerHTML = `
                                <span class="line-number">${currentLine}</span>
                                <span class="line-content">${indentStr}&nbsp;&nbsp;${escapeXml(child.nodeValue.trim())}</span>
                            `;
								container.appendChild(textLine);
								currentLine++;
							}
						}

						// 闭标签
						const closeTag = `</${tagName}>`;
						const closeLine = document.createElement('div');
						closeLine.className = 'line';
						closeLine.innerHTML = `
                        <span class="line-number">${currentLine}</span>
                        <span class="line-content">${indentStr}<span style="color: #a71d5d;">&lt;/</span><span style="color: #7952b3;">${tagName}</span><span style="color: #a71d5d;">&gt;</span></span>
                    `;
						container.appendChild(closeLine);
						currentLine++;
					}
				}
				return {
					lineNumber: currentLine
				};
			}

			// 辅助函数，用于转义 XML 中的特殊字符，防止在 HTML 中被错误解析
			function escapeXml(unsafe) {
				return unsafe.replace(/[&<>"']/g, function(c) {
					switch (c) {
						case '&':
							return '&amp;';
						case '<':
							return '&lt;';
						case '>':
							return '&gt;';
						case '"':
							return '&quot;';
						case "'":
							return '&apos;';
					}
					return c;
				});
			}

			// 自定义提示框函数
			function showToast(message) {
				const existingToast = document.querySelector('.toast');
				if (existingToast) {
					existingToast.remove();
				}

				const toast = document.createElement('div');
				toast.className = 'toast';
				toast.innerHTML = `
                <span>${message}</span>
                <span class="close-btn">×</span>
            `;
				document.body.appendChild(toast);

				setTimeout(() => {
					toast.classList.add('show');
				}, 10);

				const autoCloseTimeout = setTimeout(() => {
					toast.classList.remove('show');
					setTimeout(() => {
						toast.remove();
					}, 300);
				}, 3000);

				toast.querySelector('.close-btn').addEventListener('click', () => {
					clearTimeout(autoCloseTimeout);
					toast.classList.remove('show');
					setTimeout(() => {
						toast.remove();
					}, 300);
				});
			}
		</script>
	</body>
</html>